use std::collections::BTreeMap;

use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, LitStr};
use proc_macro2::TokenStream as T2;

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(ios_config))]
struct Attrs {  
    ios: Vec<LitStr>,
}

#[derive(Debug)]
enum Mode {
    Ain,
    OutPP,
    OutOD,
    InputFloat,
    InputUp,
    InputDown,
    AfPP,
    AfOD
}

#[derive(Debug)]
struct Port {
    pin: u8,
    port: String,
}

impl From<&str> for Port {
    fn from(value: &str) -> Self {
        let name = &value[1..2];
        let pin = &value[2..];
        Self { pin: pin.parse().unwrap(), port: name.to_string() }
    }
}

impl From<&str> for Mode {
    fn from(value: &str) -> Self {
        match value {
            "AIN" => Self::Ain,
            "OUT_PP" => Self::OutPP,
            "OUT_OD" => Self::OutOD,
            "IN_FLOAT" => Self::InputFloat,
            "IN_UP" => Self::InputUp,
            "IN_DOWN" => Self::InputDown,
            "AF_PP" => Self::AfPP,
            "AF_OD" => Self::AfOD,
            _ => panic!("invalid mode")
        }
    }
}

#[derive(Debug)]
struct Attr2 {
    name: String,
    port: Port,
    mode: Mode,
}

impl Attr2 {

    fn rcc(groups: &[&String]) -> T2 {

        let mut expand = quote! {
        };

        for group in groups {
            let field = format_ident!("iop{}en", group.to_lowercase());
            expand.extend(quote! {
                .#field().enabled()
            });
        }

        quote! {
            use stm32f1::stm32f103::RCC;

            let rcc: RCC = unsafe {
                ::core::mem::transmute(())
            };
            rcc.apb2enr.modify(|_, w| w #expand);
        }
    }

    fn init_with_group(group: &str, attrs: &[Self]) -> T2 {
        let gpio_up = format_ident!("GPIO{}", group.to_uppercase());
        let mut crl = Vec::new();
        let mut crh = Vec::new();
        let mut odr = Vec::new();
        for attr in attrs {
            if attr.port.pin < 8 {
                crl.push(attr.cr());
            } else {
                crh.push(attr.cr());
            }
            match &attr.mode {
                Mode::InputDown | Mode::InputUp => {
                    odr.push(attr.odr());
                }
                _ => {}
            };
        }

        let mut expand = quote! {};
        if !crl.is_empty() {
            expand.extend(quote! {
                gpio.crl.modify(|_, w| {
                    w #(#crl)*
                });
            });
        }
        if !crh.is_empty() {
            expand.extend(quote! {
                gpio.crh.modify(|_, w| {
                    w #(#crh)*
                });
            });
        }
        if !odr.is_empty() {
            expand.extend(quote! {
                gpio.bsrr.write(|w| {
                    w #(#odr)*
                });
            });
        }

        quote! {
            {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                #expand
            }
        }
    }

    fn odr(&self) -> T2 {

        match self.mode {
            Mode::InputUp => {
                let bs = format_ident!("bs{}", self.port.pin);
                quote! {
                    .#bs().set()
                }
            },

            Mode::InputDown => {
                let br = format_ident!("br{}", self.port.pin);
                quote! {
                    .#br().reset()
                }
            }

            _ => quote! {}
        }
    }

    fn cr(&self) -> T2 {
        let mode = format_ident!("mode{}", self.port.pin);
        let cnf = format_ident!("cnf{}", self.port.pin);

        let mode_value = match self.mode {
            Mode::Ain |
            Mode::InputFloat |
            Mode::InputUp |
            Mode::InputDown => format_ident!("input"),
            
            Mode::OutPP | 
            Mode::AfPP | 
            Mode::AfOD |
            Mode::OutOD => format_ident!("output50"),
        };

        let cnf_value = match self.mode {
            Mode::Ain | 
            Mode::OutPP => format_ident!("push_pull"),
            
            Mode::InputFloat |
            Mode::OutOD => format_ident!("open_drain"),
        
            Mode::InputUp |
            Mode::InputDown |
            Mode::AfPP => format_ident!("alt_push_pull"),

            Mode::AfOD => format_ident!("alt_open_drain")
        };

        quote! {
            .#mode().#mode_value()
            .#cnf().#cnf_value()
        }
    }

    fn io_ctrl(&self) -> T2 {

        let set_fn = format_ident!("{}_set", self.name.to_lowercase());
        let reset_fn = format_ident!("{}_reset", self.name.to_lowercase());
        let toggle_fn = format_ident!("{}_toggle", self.name.to_lowercase());
        let read_fn = format_ident!("{}_read", self.name.to_lowercase());
        let gpio_up = format_ident!("GPIO{}", self.port.port);
        let bs = format_ident!("bs{}", self.port.pin);
        let br = format_ident!("br{}", self.port.pin);
        let odr = format_ident!("odr{}", self.port.pin);
        let idr = format_ident!("idr{}", self.port.pin);
        quote! {
            pub fn #read_fn() -> bool {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.idr.read().#idr().is_high()
            }

            pub fn #set_fn() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.bsrr.write(|w| {
                    w.#bs().set()
                });
            }
            pub fn #reset_fn() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.bsrr.write(|w| {
                    w.#br().reset()
                });
            }
            pub fn #toggle_fn() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.odr.modify(|r, w| {
                    w.#odr().bit(!r.#odr().bit())
                });
            }
        }
    }

    // N1_PA0_AF_PP, N2_PC0_OUT_PP, N3_PB9_IN_FLOAT
    fn parse(ios: &[LitStr]) -> BTreeMap<String, Vec<Self>> {
        let mut map: BTreeMap<String, Vec<Attr2>> = BTreeMap::new();
        for info in ios {
            let info = info.value();
            let info = info.trim();
            let ls: Vec<_> = info.split("_").collect();
            let name = ls[0].to_string();
            let port = ls[1].to_string();
            let mut mode0 = ls[2].to_string();
            if ls.len() >= 4 {
                mode0.push_str("_");
                mode0.push_str(ls[3]);
            }

            let port = Port::from(port.as_ref());
            let group = port.port.clone();
            let vec = map.get_mut(&group);
            if let Some(vec) = vec {
                vec.push(Attr2 {
                    name,
                    port,
                    mode: Mode::from(mode0.as_ref())
                });
            } else {
                let mut vec = Vec::new();
                vec.push(Attr2 {
                    name,
                    port,
                    mode: Mode::from(mode0.as_ref())
                });
                map.insert(group, vec);
            }
        }

        map
    }
}

pub fn build(input: TokenStream) -> TokenStream {

    let input = parse_macro_input!(input as DeriveInput);
    let attrs = Attrs::from_derive_input(&input).unwrap();
    let name = &input.ident;

    let attrs = Attr2::parse(&attrs.ios);
    let groups: Vec<_> = attrs.keys().collect();

    let rcc = Attr2::rcc(&groups);
    let mut init_expand = quote! {};

    let mut io_ctrl = quote! {};

    for (group, attrs) in attrs {
        let ret = Attr2::init_with_group(&group, &attrs);
        init_expand.extend(ret);
        for attr in attrs.iter() {
            io_ctrl.extend(attr.io_ctrl());
        }
    }

    let expand = quote! {

        impl #name {

            pub fn gpio_group_init() {
                #rcc
                #init_expand
            }
            #io_ctrl
        }
    };
    expand.into()
}